﻿// Copyright (C) Jari Hautio <jari.hautio@iki.fi> 2008. Licensed under GPLv2. See LICENSE.txt in solution folder.
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace ColorLib
{
    /// <summary>
    /// Class for holding various methods to do color calculations.
    /// </summary>
    public class ColorMath
    {
        public static double[] ToYbCr(Color c1)
        {
            double Y1 =    0 + 0.299    * c1.R + 0.587   * c1.G  + 0.114    * c1.B;
            double cb1 = 128 - 0.168736 * c1.R - 0.331264 * c1.G + 0.5      * c1.B;
            double cr1 = 128 + 0.5      * c1.R - 0.418688 * c1.G - 0.081312 * c1.B;
            return new double[] { Y1, cb1, cr1 };
        }
        public static double[] ToYbCrFast(Color c1)
        {
            double Y1 = 0 + ((77 * c1.R + 150 * c1.G + 29 * c1.B) / 256.0);
            double cb1 = 128 + ((-43 * c1.R - 85 * c1.G + 128 * c1.B) / 256.0);
            double cr1 = 128 + ((128 * c1.R - 107 * c1.G - 21 * c1.B) / 256.0);
            return new double[] { Y1, cb1, cr1 };
        }
        public static double DistanceYCbCr(Color c1, Color c2)
        {
            double Y1 =    0 + 0.299    * c1.R + 0.587   * c1.G  + 0.114    * c1.B;
            double cb1 = 128 - 0.168736 * c1.R - 0.331264 * c1.G + 0.5      * c1.B;
            double cr1 = 128 + 0.5      * c1.R - 0.418688 * c1.G - 0.081312 * c1.B;
            double Y2 =    0 + 0.299    * c2.R + 0.587    * c2.G + 0.114    * c2.B;
            double cb2 = 128 - 0.168736 * c2.R - 0.331264 * c2.G + 0.5      * c2.B;
            double cr2 = 128 + 0.5      * c2.R - 0.418688 * c2.G - 0.081312 * c2.B;
            return
                Math.Pow(Y1 - Y2, 2) + Math.Pow(cb1 - cb2, 2) + Math.Pow(cr1 - cr2, 2);
        }

        public static double DistanceYCbCrFast(Color c1, Color c2)
        {
            double Y1 = 0 + ((77 * c1.R + 150 * c1.G + 29 * c1.B) /256.0);
            double cb1 = 128 + ((-43 * c1.R - 85 * c1.G + 128 * c1.B) /256.0);
            double cr1 = 128 + ((128 * c1.R - 107 * c1.G - 21 * c1.B) /256.0);
            double Y2 = 0 + ((77 * c2.R + 150 * c2.G + 29 * c2.B) /256.0);
            double cb2 = 128 + ((-43 * c2.R - 85 * c2.G + 128 * c2.B) /256.0);
            double cr2 = 128 + ((128 * c2.R - 107 * c2.G - 21 * c2.B) /256.0);
            return
                Math.Pow(Y1 - Y2, 2) + Math.Pow(cb1 - cb2, 2) + Math.Pow(cr1 - cr2, 2);
        }
        public static double DistanceRGBHSV(Color c1, Color c2)
        {
            double d1 = c1.GetHue() - c2.GetHue();
            double d2 = c1.GetHue() > c2.GetHue() ? c1.GetHue() - 360 - c2.GetHue() : c2.GetHue() - 360 - c1.GetHue();
            d1 = Math.Pow(d1, 2);
            d2 = Math.Pow(d2, 2);
            double hsv = 4 * Math.Min(d1, d2) *
                c1.GetSaturation() * c2.GetSaturation() *
                (0.5 - Math.Abs(0.5 - c1.GetBrightness())) *
                (0.5 - Math.Abs(0.5 - c2.GetBrightness()));
            return hsv +
                (Math.Pow((c1.R - c2.R), 2) +
                 Math.Pow((c1.G - c2.G), 2) +
                 Math.Pow((c1.B - c2.B), 2));

        }

        public static double DistanceRGB(Color c1, Color c2)
        {
            return
                Math.Pow((c1.R - c2.R), 2) +
                Math.Pow((c1.G - c2.G), 2) +
                Math.Pow((c1.B - c2.B), 2);
        }
        public static double DistanceUYV(Color c1, Color c2)
        {
            // convert fitst to uyv format
            double Y1 = 0.299 * c1.R + 0.587 * c1.G + 0.114 * c1.B;
            double U1 = 0.492 * (c1.B - Y1);
            double V1 = 0.877 * (c1.R - Y1);
            double Y2 = 0.299 * c2.R + 0.587 * c2.G + 0.114 * c2.B;
            double U2 = 0.492 * (c2.B - Y1);
            double V2 = 0.877 * (c2.R - Y1);
            return
                Math.Pow(Y1 - Y2, 2) + Math.Pow(U1 - U2, 2) + Math.Pow(V1 - V2, 2);
        }
        public static double DistanceHue(Color c1, Color c2)
        {
            double d1 = c1.GetHue() - c2.GetHue();
            double d2 = c1.GetHue() > c2.GetHue() ? c1.GetHue() - 360 - c2.GetHue() : c2.GetHue() - 360 - c1.GetHue();
            d1 = Math.Pow(d1, 2);
            d2 = Math.Pow(d2, 2);
            double hsv = 4 * Math.Min(d1, d2) *
                c1.GetSaturation() * c2.GetSaturation() *
                (0.5 - Math.Abs(0.5 - c1.GetBrightness())) *
                (0.5 - Math.Abs(0.5 - c2.GetBrightness()));
            return hsv;
        }
        public static double SrgbLinearize(byte c)
        {
            double d = c / 255.0;
            if (d <= 0.04045)
                return d / 12.92;
            return Math.Pow((d+0.055)/1.055, 2.4);
        }
        public static byte SrgbDelinearize(double c)
        {
            double d =0;
            if (c<=0.0031308)
                d = 12.92*c;
            else d = (1.055)*Math.Pow(c,1.0/2.4)-0.055;
            if (d < 0) return 0;
            if (d >1.0) return 255; 
            return Convert.ToByte( 255 * d);
        }
        public static byte RgbDelinearize(double c)
        {
            double d = Math.Pow(c,1.0/2.2);
            if (d < 0) return 0;
            if (d >=1.0) return 255; 
            return Convert.ToByte( Math.Round(255 * d));
        }

        public static Color CieToRgb(double x, double y, double z)
        {
            double r =  3.2406 * x - 1.5372 * y - 0.4986 * z;
            double g = -0.9689 * x + 1.8758 * y + 0.0415 * z;
            double b = 0.0557 * x - 0.2040 * y + 1.0570 * z;
            return Color.FromArgb(255,RgbDelinearize(r), RgbDelinearize(g), RgbDelinearize(b)) ;
        }
        public static double[] RgbToCie(Color c)
        {
            double cR = Math.Pow(c.R/255.0, 2.2);
            double cG = Math.Pow(c.G/255.0, 2.2);
            double cB = Math.Pow(c.B/255.0, 2.2);


            double[] r = new double[3];
            r[0] = 0.4124*cR + 0.3576*cG + 0.1805*cB;
            r[1] = 0.2126*cR + 0.7152*cG + 0.0722*cB;
            r[2] = 0.0193*cR + 0.1192*cG + 0.9505*cB;
            return r;
        }
        private static double LabF(double t)
        {
            if (t > 0.008856451)
                return Math.Pow(t, 1.0 / 3.0);
            return 7.787037 * t + 4.0 / 29.0;
        }
        private static double LabFi(double t)
        {
            if (t >0.20689655)
                return Math.Pow(t,  3.0);
            return (t - 4.0 / 29.0) / 7.787037;
        }
        public static double[] RgbToLab(Color c)
        {
            const double Xn = 1;// 0.9505; 
            const double Yn = 1;
            const double Zn = 1;// 1.089;
            double[] r = RgbToCie(c);
            double L = 116 * LabF(r[1] / Yn) - 16;
            double a = 500 * (LabF(r[0] / Xn) - LabF(r[1] / Yn));
            double b = 200 * (LabF(r[1] / Yn) - LabF(r[2] / Zn));
            r[0] = L;
            r[1] = a;
            r[2] = b;
            return r;
        }
        public static Color LabToRgb(double [] lab)
        {
            const double Xn = 1;// 0.9505; 
            const double Yn = 1;
            const double Zn = 1;// 1.089;
            double l = 1.0/116*(lab[0]+16);

            double y = Yn*LabFi(l);
            double x = Xn * LabFi(l + lab[1] / 500.0);
            double z = Zn * LabFi(l - lab[2] / 200.0);

            return CieToRgb(x, y, z);
        }
        public static Color RgbToLabColor(Color c)
        {
            Color lc;
            double [] lab = RgbToLab(c);
            lc = Color.FromArgb(255,
                Convert.ToByte(Math.Round(lab[0])),
                Convert.ToByte(Math.Round(lab[1]) + 128),
                Convert.ToByte(Math.Round(lab[2]) + 128));
            return lc;
        }
        public static Color LabColorToRgb(Color c)
        {
            double [] lab={ c.R, c.G-128.0, c.B - 128.0};
            return LabToRgb(lab);
        }
        public static double DistanceLab(Color c1, Color c2)
        {
            double[] a = RgbToLab(c1);
            double[] b = RgbToLab(c2);
            return
                Math.Pow((a[0]-b[0]), 2) +
                Math.Pow((a[1] - b[1]), 2) +
                Math.Pow((a[2] - b[2]), 2);
        }
    }
}
